/*
 * Copyright 2010 Spolecne s.r.o. (www.spoledge.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.appengine.api.datastore;

import java.io.Serializable;

import java.util.ArrayList;
import java.util.Iterator;


/**
 * This is a GWT emulation class.
 * It is ONLY used on the client side - JavaScript.
 *
 * This class is intended to use with custom DTO serializers
 * generated by AuDAO.
 *
 * Method hashCode() may differ from the original one.
 */
public class Key implements Serializable, Comparable<Key> {

    static final long serialVersionUID = -448150158203091507L;

    private Key parentKey;
    private String kind;
    private long id;
    private String name;


    ////////////////////////////////////////////////////////////////////////////
    // Constructors
    ////////////////////////////////////////////////////////////////////////////

    @SuppressWarnings("unused")
    private Key() {
    }


    Key( String kind, Key parentKey, long id, String name ) {
        if (kind == null || kind.length() == 0)
            throw new IllegalArgumentException("Kind cannot be empty");

        if (name != null) {
            if (name.length() == 0) throw new IllegalArgumentException("Name cannot be empty");
            if (id != 0) throw new IllegalArgumentException("Cannot specify both id and name at once");
        }

        this.kind = kind;
        this.parentKey = parentKey;
        this.id = id;
        this.name = name;
    }


    ////////////////////////////////////////////////////////////////////////////
    // Public
    ////////////////////////////////////////////////////////////////////////////

    public String getKind() {
        return kind;
    }

    public Key getParent() {
        return parentKey;
    }

    public String getName() {
        return name;
    }

    public long getId() {
        return id;
    }


    public boolean isComplete() {
        return name != null || id != 0;
    }


    public Key getChild( String kind, long id ) {
        return new Key( kind, this, id, null );
    }


    public Key getChild( String kind, String name ) {
        return new Key( kind, this, 0, name );
    }


    ////////////////////////////////////////////////////////////////////////////
    // Comparable
    ////////////////////////////////////////////////////////////////////////////

    public int compareTo( Key o ) {
        if (this == o) return 0;

        Iterator<Key> path = reverse( this ).iterator();
        Iterator<Key> oPath = reverse( o ).iterator();

        while (path.hasNext()) {
            Key key = path.next();
             
            if (oPath.hasNext()) {
                Key oKey = oPath.next();
                
                int ret = compareToInternal( key, oKey );

                if (ret != 0) return ret;
            }
            else {
                return 1;
            }
        }

        return oPath.hasNext() ? -1 : 0;
    }


    ////////////////////////////////////////////////////////////////////////////
    // Misc
    ////////////////////////////////////////////////////////////////////////////

    public boolean equals( Object o ) {
        if (this == o) return true;
        if (o == null || (!(o instanceof Key))) return false;

        Key k = (Key) o;

        if ( id != k.id ) return false;
        if ( !kind.equals( k.kind )) return false;
        if ( name != null ) {
            if ( !name.equals( k.name )) return false;
        }
        else {
            if ( k.name != null ) return false;
            if ( id == 0 ) return false; // incomplete keys are different
        }

        if ( parentKey != null ) {
            if ( !parentKey.equals( k.parentKey )) return false;
        }
        else if ( k.parentKey != null ) return false;

        return true;
    }


    public int hashCode() {
        int ret = kind.hashCode();

        if ( parentKey != null ) ret += parentKey.hashCode() * 29;

        if (name != null) {
            ret = 29 * ret + name.hashCode();
        }
        else {
            ret = 29 * ret + (int)( id ^ (id >>> 32));
        }

        return ret;
    }


    public String toString() {
        StringBuilder sb = new StringBuilder();
        appendToString( sb );

        return sb.toString();
    }


    ////////////////////////////////////////////////////////////////////////////
    // Private
    ////////////////////////////////////////////////////////////////////////////

    private void appendToString( StringBuilder sb ) {
        if ( parentKey != null ) {
            parentKey.appendToString( sb );
            sb.append( '/' );
        }

        sb.append( kind );
        sb.append( '(' );

        if (name != null) sb.append('"').append( name ).append( '"' );
        else sb.append( id );

        sb.append( ')' );
    }


    private static ArrayList<Key> reverse( Key key ) {
        ArrayList<Key> ret = key.getParent() != null ? reverse( key.getParent()) : new ArrayList<Key>();
        ret.add( key );

        return ret;
    }


    private static int compareToInternal( Key k1, Key k2 ) {
        if (k1 == k2) return 0;

        int ret = k1.getKind().compareTo( k2.getKind());
        if (ret != 0) return ret;

        if (k1.getId() != 0) {
            if (k2.getId() == 0) return -1;
            else return Long.valueOf( k1.getId()).compareTo( Long.valueOf( k2.getId()));
        }

        if (k2.getId() != 0) return 1;
        if (k1.getName() != null) return k1.getName().compareTo(k2.getName());
        return k2.getName() == null ? 0 : 1;
    }

}

